/**
 * ex8. 结构体
 *
 * @author cyy
 * @date 2020/11/4
 */
package main

import (
	"encoding/json"
	"fmt"
)

type myStr string

// 定义结构体时允许成员字段没有字段名，这些字段称为匿名字段，且类型必须唯一
type Nameless struct {
	string
	int
	*Address
}

type Person struct {
	Name string
	age  int
	// Addr Address
	*Address
}

type Address struct {
	Country string
	City    string
}

type Student struct {
	ID     int             `json:"id"` // 定义tag做json的kv映射
	Person `json:"person"` // 继承Person类
}

// 结构体的自定义方法
func (this Person) PrintInfo() {
	fmt.Printf("%#v\n", this)
}

func (this *Person) SetInfo(name string, age int) {
	this.Name = name
	this.age = age
}

// 自定义类型的自定义方法
func (m myStr) PrintMyStr() {
	fmt.Println(m)
}

func main() {
	/*
		结构体
			实例化方式
				var p0 *Person
				var p1 = new(Person)
				var p2 = &Person{}
				var p3 = &Person{ // 简化声明 p3 := &Person{
		 			"李四",
					18,
					nil,
				}
				var p4 = &Person{ // 简化声明 p3 := Person{ "李四", 18 } // 如果实例化属性在同一行的话最后一个属性末尾可以不用加”,“

			在go中支持对结构体指针直接使用”.“来访问结构体成员，例如 p1.name 在底层实际上是 (*p2).name
			注意：结构体首字母大写，表示这个结构体是公有的，反之私有，只有当前包可以使用，结构体内的属性同理
	*/
	// 匿名字段结构体
	n := Nameless{
		string: "",
		Address: &Address{
			Country: "",
			City:    "",
		},
	}
	n.int = 1
	n.Address = &Address{}

	// var p0 Person
	var p1 = new(Person)
	// var p2 = &Person{}
	p1.Name = "张三"
	(*p1).age = 20 // 如果是使用第一种方式声明的p0，则不可以使用指针的方式
	// 当访问结构体成员时，会先在结构体中查找该字段，找不到再去匿名结构中查找
	// 如果多个嵌套结构体内都包含相同的字段，会报错 ambiguous selector
	p1.Address = &Address{}
	p1.City = "浙江"
	p1.Address.City = "浙江"
	fmt.Printf("值：%v，类型：%T\n", p1, p1)
	// 建议使用 %#v 格式化输出结构体的值
	fmt.Printf("值：%#v，类型：%T\n", p1, p1)

	// 采用该种方式实例化时，属性名称要么都写，要么都省略
	var p3 = &Person{
		/* Name: */ "李四",
		/* age:  */ 18,
		&Address{
			Country: "中国",
		},
	}
	fmt.Printf("值：%#v，类型：%T\n", p3, p3)

	var p4 = &Person{"李四", 18, &Address{Country: "中国"}}
	fmt.Printf("值：%#v，类型：%T\n", p4, p4)

	// 结构体是值类型
	p5 := p3
	p5.Name = "王五"
	fmt.Println(p3.Name == p5.Name) // false

	/*
		结构体方法和接收者
			定义格式
				func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {
					// 方法体
				}
			在go中，没有类的概念，但是可以给类型（结构体、自定义类型）定义方法 —— 定义了接收者的函数。接收者就类似java中的this。

	*/
	p5.SetInfo("哈哈", 22)
	p5.PrintInfo()

	/*
		继承
			go中的继承令人看了无语😅，通过嵌套实现
	*/
	s1 := &Student{
		ID: 0,
		Person: Person{
			Name:    "小红",
			age:     22,
			Address: &Address{},
		},
	}
	// 子类调用父类的方法
	s1.PrintInfo()

	/*
		结构体与json的相互转换
			私有属性不能被序列化
	*/
	// 结构体转json
	jsonByte, _ := json.Marshal(s1)
	jsonStr := string(jsonByte)
	fmt.Println(jsonStr)
	// json转结构体
	var s2 *Student
	err := json.Unmarshal([]byte(jsonStr), &s2)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Printf("%#v\n", s2)

}
